#
# create_vcw.py
#   by Jaakko Salli
#
# Creates virtual class wrapper code. Used for wxPropertyGrid's wxPython
# bindings. It should be run whenever new overriddable virtual functions
# have been added in wxPropertyGrid.
#
# Remarks: Only works if you have VC++ compiler in your path (it is used
#          for pre-compiling the header files).
#
# Any attempt to understand this code may lead to insanity. So, you have been
# warned.
#

import sys, os, os.path, time, re, pdb, imp

import cpp_header_parser

custom_macros = []

SCRIPT_NAME = 'create_vcw'

#
# Load settings
#

try:
    settings_filename = sys.argv[1]
except IndexError:
    settings_filename = 'vcw_config.py'

try:
    settings = imp.load_source('settings', settings_filename)
except (ImportError, IOError):
    import traceback
    traceback.print_exc()
    print 'ERROR: %s was not found or had a syntax error.'%settings_filename
    sys.exit(1)


ALLOWED_CLS_CONFIG_KEYS = frozenset(['excluded_methods',
                                     '__init__append',
                                     'script_object_member'])


re_cdefine = re.compile('^\s*#\s*define\s+.+$', re.I | re.M)
re_swig_ignore = re.compile('^\s*%\s*ignore\s+(.+?)\;*$', re.I | re.M)


#
# Define some typemap templates
#

template_typemap_ptr_in = """\
    if ( !SWIG_IsOK(SWIG_ConvertPtr(%s, (void**)&(%s%s), SWIGTYPE_p_%s, 0)) ) {
        PyErr_SetString(PyExc_TypeError,"expected %s");
        SWIG_fail;
    }
"""

template_typemap_cb_ptr_out = """\
    $result = NULL;
    if ( $1->%(script_object_member)s ) $result = (PyObject*)$1->%(script_object_member)s;
    if ( $result ) Py_INCREF($result);
    else $result = SWIG_NewPointerObj((void*)$1, SWIGTYPE_p_%(class_name)s, 0);
"""

#    errval = SWIG_ConvertPtr($input, (void**)&$1, SWIGTYPE_p_%s, 0);
template_typemap_cb_ptr_in = """\
    if ( !wxPyConvertSwigPtr($input, (void**)&$1, wxT("%s")) ) {
        PyErr_SetString(PyExc_TypeError,"expected %s");
        SWIG_fail;
    }
"""

typemap_in_bool = """\
    if ( !SWIG_IsOK(SWIG_AsVal_bool($input, &$1)) ) {
        PyErr_SetString(PyExc_TypeError,"expected bool");
        SWIG_fail;
    }
"""


class creator_app:
    def __init__(self):
        self.callback_classes = set()  # Set of classes that have virtual method callbacks

        # List C defines found in the included .i files
        self.c_define_lines = []

        # Set of SWIG ignore directives found
        self.swig_ignores = set()

        # Keys are types ('in', 'out', etc.) keys are first_arg_type:content dicts.
        self.typemaps = {'in':{},'out':{},'freearg':{},'varout':{},'typecheck':{},
                         'argout':{},'argin':{}}
        self.add_builtin_typemaps()

        # Swig .i code lines
        self.scl = ['// THIS FILE HAS BEEN AUTO-GENERATED BY %s' % \
                    (SCRIPT_NAME.upper())]

        settings.include_paths = [os.path.normpath(os.path.abspath(s)) \
                                  for s in settings.include_paths]

        # Make sure include paths exist
        for s in settings.include_paths:
            if not os.path.isdir(s):
                raise ValueError("Invalid include path: '%s'"%s)

        settings.include_paths.append(settings.work_dir)


    def find_typemap(self, maptype, arg_list, pos=0):

        ta = arg_list[pos][0]

        try:
            ls = self.typemaps[maptype][ta]
        except KeyError:
            ls = []

        args_available = len(arg_list) - pos

        for tpl in ls:

            # In all cases, first type matches already

            tpl0 = tpl[0]
            if len(tpl0) <= args_available:
                matches = True

                pos_ = pos

                for tm, nm in tpl0:
                    ta, na = arg_list[pos_]
                    if tm != ta or (len(nm) and (nm != na)):
                        #if ta.find('String') >= 0:
                        #    print "'%s' '%s' '%s' '%s'"%(tm, nm, ta, na)
                        matches = False
                        break
                    pos_ += 1

                if matches:
                    return tpl

        return None


    def process_typemap_in(self, tm_tpl, t, varnames, py_varnames):
        tm_datatypes, tm_tempdata, tm_content = tm_tpl

        s = self.process_typemap_common(tm_tpl, t)

        s = s.replace('$input',py_varnames[0])
        for i in range(len(tm_datatypes)):
            s = s.replace('$%i'%(i+1),varnames[i])

        return s


    def process_typemap_out(self, tm_tpl, t, varnames, py_varnames):
        tm_datatypes, tm_tempdata, tm_content = tm_tpl

        s = self.process_typemap_common(tm_tpl, t)

        s = s.replace('$result',py_varnames[0])
        for i in range(len(tm_datatypes)):
            s = s.replace('$%i'%(i+1),varnames[i])

        return s


    def process_typemap_common(self, tm_tpl, t):

        argnum = 0
        s = tm_tpl[2].replace('$owner','0').replace('%#','#').replace('$argnum','%i'%argnum)

        for tt,tn,tv in tm_tpl[1]:
            if tv:
                s2 = '    %s %s = %s;'%(tt,tn,tv)
            else:
                s2 = '    %s %s;'%(tt,tn)
            s = s2 + s.replace('$%s'%tn,'temp%i'%argnum)

        if t.endswith('&'):
            s = s.replace('$1','(&$1)')

        #s = cpp_header_parser.indent(s,4)

        if s[-1] != '\n':
            s += '\n'

        """if lb_count > 1:
            s = '{\n%s\n}'%s
            s = cpp_header_parser.indent(s,4)+'\n'
        el"""
        #if lb_count == 0:
        #    s += '\n'

        return s


    def get_swig_typeconv_in(self, arg_list, py_varnames = None, is_callback = False):
        i = 0
        prepss = []
        unprepss = []
        retvalprepss = []

        # Generate python-equivalent of variable-names
        if not py_varnames:
            if not is_callback:
                # These are py-to-c argument conversions
                py_varnames = ['py_%s'%(a[1]) for a in arg_list]
            else:
                # These are py-to-c return value conversions
                py_varnames = ['res']*len(arg_list)

        while i < len(arg_list):
            t, varname = arg_list[i]

            py_varname = py_varnames[i]

            mode = 'in'
            tpl = self.find_typemap(mode, arg_list, i)

            std_unprep = None

            # If not found at first, try the 'argin'
            # typemap.
            if tpl is None:
                if not is_callback:
                    mode = 'argout'
                    tpl = self.find_typemap(mode, arg_list, i)

            tcls,flags = cpp_header_parser.parse_type(t)

            if not (tpl is None):
                #tm_datatypes,tm_tempdata,tm_content = tpl
                tm_datatypes = tpl[0]

                tms = self.process_typemap_in(tpl, tcls, [a_[1] for a_ in arg_list[i:]],py_varnames[i:])
                if mode == 'in':
                    prepss.append(tms)
                    unprepss.append(std_unprep)
                    retvalprepss.append(None)
                else:
                    prepss.append(None)
                    # argin typemap requires some extra preparations
                    unprepss.append(None)
                    retvalprepss.append(tms)

                for i_ in range(1, len(tm_datatypes)):
                    prepss.append(None)
                    unprepss.append(None)
                    retvalprepss.append(None)

                i += len(tm_datatypes)
            else:
                if 'ptr' in flags or 'ref' in flags:

                    if 'ptr' in flags:
                        amp_s = ''
                    else:
                        amp_s = '&'

                    prepss.append(template_typemap_ptr_in%(py_varname,amp_s,varname,tcls,tcls))

                    unprepss.append(std_unprep)
                    retvalprepss.append(None)
                else:
                    prepss.append('    ??? (typemap for %s not found)'%(t));
                    unprepss.append(None)
                    retvalprepss.append(None)

                i += 1

            # In callbacks, these are return values, so release ownership
            # of a pointer. Otherwise, just simply decrement the refcount.
            if is_callback:
                if 'ptr' in flags:
                    prepss[-1] = prepss[-1] + '    PyObject_SetAttrString(%s, "thisown", Py_False);\n    Py_DECREF(%s);'%(py_varname,py_varname)
                else:
                    prepss[-1] = prepss[-1] + '    Py_DECREF(%s);'%(py_varname)

        return (prepss,unprepss,retvalprepss)


    def get_swig_typeconv_out(self, arg_list, py_varnames = None, is_callback = False):

        i = 0
        prepss = []
        unprepss = []
        retvalprepss = []
        sig_ss = []  # Signature strings

        # Generate python-equivalent of variable-names
        if not py_varnames:
            if is_callback:
                # These are c-to-py argument conversions
                py_varnames = ['py_%s'%(a[1]) for a in arg_list]
            else:
                # These are c-to-py return value conversions
                py_varnames = ['res']*len(arg_list)

        while i < len(arg_list):
            t, varname = arg_list[i]
            mode = 'out'

            tpl = self.find_typemap(mode, arg_list, i)

            if t.find('String') >= 0:
                #print 'NOT FOUND FOR: %s'%(str(arg_list[i]))
                #pdb.set_trace()
                tpl = self.find_typemap(mode, arg_list, i)

            std_unprep = '    Py_DECREF(py_%s);'%varname

            # If not found at first, try the 'argin'
            # typemap.
            if tpl is None:
                if is_callback:
                    tpl = self.find_typemap('argin', arg_list, i)

                    if not tpl:
                        # Try to generate 'argin'-style typemap for likely callback arguments
                        if cpp_header_parser.is_likely_callback_arg(t):
                            # basic 'in' typemap is required
                            tpl = self.generate_argin_typemap(t)
                            mode = 'argin'
                    else:
                        mode = 'argin'

            if not (tpl is None):
                #tm_datatypes,tm_tempdata,tm_content = tpl
                tm_datatypes = tpl[0]

                sig_ss += [('%s %s'%a_) for a_ in tm_datatypes]
                arg_names = [a_[1] for a_ in arg_list[i:]]

                if mode == 'out':
                    tms = self.process_typemap_out(tpl, t, arg_names, py_varnames[i:])
                    prepss.append(tms)
                    unprepss.append(std_unprep)
                    retvalprepss.append(None)
                else:
                    resn = 'res'

                    tms = self.process_typemap_in(tpl, t, [('*%s'%a) for a in arg_names], [resn])
                    prepss.append(None)
                    unprepss.append(None)

                    if not cpp_header_parser.is_builtin_type(t):
                        tms += '    PyObject_SetAttrString(%s, "thisown", Py_False);\n    Py_DECREF(%s);'%(resn,resn)
                    else:
                        tms += '    Py_DECREF(%s);'%(resn)

                    retvalprepss.append(tms)

                for i_ in range(1, len(tm_datatypes)):
                    prepss.append(None)
                    unprepss.append(None)
                    retvalprepss.append(None)

                i += len(tm_datatypes)
            else:
                sig_ss.append(t)

                tcls,flags = cpp_header_parser.parse_type(t)
                if 'ptr' in flags or 'ref' in flags:

                    if 'ptr' in flags:
                        ampersand_s = ''
                    else:
                        ampersand_s = '&'

                    resn = 'py_%s'%varname

                    prepss.append('    %s = SWIG_NewPointerObj((void*)%s%s, SWIGTYPE_p_%s, 0);'%(resn,ampersand_s,varname,tcls))
                    unprepss.append(std_unprep)
                    retvalprepss.append(None)
                else:
                    prepss.append('    ??? (typemap for %s not found)'%(t));
                    unprepss.append(None)
                    retvalprepss.append(None)

                i += 1

        return (prepss,unprepss,retvalprepss,' '.join(sig_ss))


    def add_simple_typemap(self, tmt, dt, s):
        self.typemaps[tmt][dt] = [(((dt,''),),[],'    '+s)]


    def add_generated_typemap(self, tmt, dt, s):
        """\
        Difference to add_simple_typemap, is that this adds to SWIG .i code as well.
        """
        self.add_simple_typemap(tmt, dt, s)
        self.scl.append('\n%%typemap(%s) %s {\n%s\n}'%(tmt,dt,s))


    def add_builtin_typemaps(self):
        tms = self.typemaps
        self.add_simple_typemap('out', 'int', '$result = PyInt_FromLong((long)$1);')
        self.add_simple_typemap('out', 'long', '$result = PyInt_FromLong($1);')
        self.add_simple_typemap('out', 'unsigned int', '$result = PyInt_FromLong((long))$1);')
        self.add_simple_typemap('out', 'unsigned long', '$result = PyInt_FromLong((long)$1);')
        self.add_simple_typemap('out', 'size_t', '$result = PyInt_FromLong((size_t)$1);')
        self.add_simple_typemap('out', 'bool', '$result = PyBool_FromLong((long)$1);')
        self.add_simple_typemap('in', 'int', '$1 = (int)PyInt_AS_LONG($input);')
        self.add_simple_typemap('in', 'long', '$1 = (long)PyInt_AS_LONG($input);')
        self.add_simple_typemap('in', 'unsigned int', '$1 = (unsigned int)PyInt_AS_LONG($input);')
        self.add_simple_typemap('in', 'unsigned long', '$1 = (unsigned long)PyInt_AS_LONG($input);')
        self.add_simple_typemap('in', 'size_t', '$1 = (size_t)PyInt_AS_LONG($input);')
        self.add_simple_typemap('in', 'bool', typemap_in_bool)


    def add_typemap(self, tm_type, tm_datatypes, tm_tempdata, tm_content):
        """
        Add arbitrary typemap to table.
        """

        tpl_ = (tm_datatypes,tm_tempdata,tm_content)

        if len(tm_datatypes) == 0:
            print 'ERROR: Empty tm_datatypes (%s, %s, %s, %s...)'%(tm_type, tm_datatypes, tm_tempdata, tm_content[:32])
            return

        print("Adding '%s' typemap for %s" % (tm_type, tm_datatypes))

        dt_count = len(tm_datatypes)
        base_t = tm_datatypes[0][0]

        try:
            self.typemaps[tm_type][base_t].append(tpl_)
        except KeyError:
            self.typemaps[tm_type][base_t] = [tpl_]

        if dt_count == 1:
            # Add const typemap as well
            if not base_t.startswith('const'):
                t2 = "const %s"%base_t
                tpl2 = ([(t2,'')],tm_tempdata,tm_content)
                if not self.typemaps[tm_type].has_key(t2):
                    self.typemaps[tm_type][t2] = [tpl2]

            # Add 'plain' typemap, for references
            if base_t.endswith('&'):
                t2 = base_t[:-1]
                if not self.typemaps[tm_type].has_key(t2):
                    s = ''
                    if tm_type == 'in':
                        if len(tm_tempdata) > 0:
                            print '%s tempdata: %s'%(base_t,tm_tempdata)
                            if tm_tempdata[0][0] == t2:
                                tt = '$1 = &%s;'%(tm_tempdata[0][1])
                                if tm_content.find(tt) >= 0:
                                    s = tm_content.replace(tt,'%s* _tptr_$argnum = &$_1;'%t2).replace('$1','_tptr_$argnum').replace('$_1','$1')

                    if s:
                        tpl2 = ([(t2,'')],tm_tempdata,s)
                        self.typemaps[tm_type][t2] = [tpl2]

                # Add ptr type map as wll
                t2 = base_t[:-1]+'*'
                if not self.typemaps[tm_type].has_key(t2):
                    tpl2 = ([(t2,'')],tm_tempdata,tm_content)
                    self.typemaps[tm_type][t2] = [tpl2]

                # And also a const one
                t2 = 'const '+base_t[:-1]+'*'
                if not self.typemaps[tm_type].has_key(t2):
                    tpl2 = ([(t2,'')],tm_tempdata,tm_content)
                    self.typemaps[tm_type][t2] = [tpl2]

        # If this is ref typemap, generate ptr and plain variable versions
        #if dt_count == 1 and base_t.endswith('&'):
        #    self.typemaps[tm_type][base_t[:-1]+'*'].append(tpl_)

        return tpl_


    def generate_argin_typemap(self, t):
        if t[-1] != '*':
            raise ValueError('can only generate argin typemap for pointers')

        t_np = t[:-1]

        tpl = self.find_typemap('in', [(t_np,'')], 0)
        if not tpl:
            # Generate 'in' now
            tm_tempdata = []
            tm_content = template_typemap_cb_ptr_in%(t,t)
        else:
            tm_datatypes, tm_tempdata, tm_content = tpl

        #s = tm_content.replace('$1','(*$1)')
        s = tm_content

        tm_datatypes = [(t,'')]

        return self.add_typemap('argin', tm_datatypes, tm_tempdata, s)


    def add_callback_class(self, class_name, py_class_name, script_object_member): #, super_classes):
        self.callback_classes.add(class_name)

        # Add typemaps
        s = template_typemap_cb_ptr_out%{'class_name':class_name,
                                         'script_object_member':script_object_member}
        self.add_generated_typemap('out', '%s*'%class_name, s)
        s = template_typemap_cb_ptr_in%(class_name,class_name)
        self.add_generated_typemap('in', '%s*'%class_name, s)

        # Add basic SWIG code
        scl = self.scl
        #if super_classes:
        #    sucls = ' : %s'%(', '.join(['public %s'a for a in super_classes]))
        #else:
        #    sucls = ''


    def load_file(self, fn):
        """\
        Loads file named fn, searching for it in the include paths.
        """

        for inc_path in settings.include_paths:
            path = os.path.join(inc_path, fn)

            if os.path.isfile(path):
                f = file(path,'rt')
                s = f.read()
                f.close()
                return s

        raise AssertionError("Failed to load file '%s'. It was not found in "
                             "any of the following paths: %s" % \
                             (fn, ', '.join(settings.include_paths)))

        return None

    def process_include_files(self):
        from cpp_header_parser import split_argument_list, indent
        from cpp_header_parser import find_balanced_parenthesis

        re_typemap = re.compile('%typemap\s*\(\s*([^()]+)\s*\)([^{]+){',re.I)

        for fn in settings.includes:

            print("Loading include '%s'" % fn)

            s = self.load_file(fn)

            if s:
                s = cpp_header_parser.purge_comments(s)

                # Find C++ defines
                for m in re_cdefine.finditer(s):
                #{
                    c_define = m.group(0).strip()
                    if c_define[-1] == '\\':
                    #{
                        raise NotImplementedError('Multi-line defines not '
                                                  'yet supported')
                    #}
                    self.c_define_lines.append(c_define)
                #}

                # Find SWIG ignore directives
                for m in re_swig_ignore.finditer(s):
                #{
                    self.swig_ignores.add(m.group(1).strip())
                #}

                s = cpp_header_parser.process_and_run_macros(s,
                    settings.include_paths,
                    is_swig=True)

                # Load typemaps
                pos = 0
                while 1:
                    m = re_typemap.search(s, pos)
                    if not m:
                        break

                    # Get type options
                    tts = [s_.strip() for s_ in m.group(1).split(',')]
                    tm_type = tts[0]
                    tm_options = tts[1:]

                    tm_sdatatype = m.group(2)

                    parens_pos = tm_sdatatype.find('(')
                    if parens_pos >= 0:
                        # There is either multiple datatypes, or temp arg list.
                        pre_p = tm_sdatatype[:parens_pos].strip()
                        if len(pre_p) > 0:
                            # Temp arg list
                            tm_datatypes = split_argument_list(pre_p)
                            tm_tempdata,i_ = split_argument_list(tm_sdatatype,
                                parens_pos, def_vals = True)
                        else:
                            # Multiple args
                            tm_datatypes,i_ = split_argument_list(tm_sdatatype,
                                parens_pos)
                            tm_tempdata = []
                    else:
                        tm_datatypes = split_argument_list(tm_sdatatype)
                        tm_tempdata = []

                    tm_start = m.end()-1

                    tm_end = find_balanced_parenthesis(s, tm_start, '{', '}')

                    tm_content = indent(s[tm_start+1:tm_end-1],4).rstrip()

                    #print tm_type, tm_datatypes
                    #print tm_options
                    #print tm_tempdata
                    #print tm_content

                    #
                    # FIXME: This is a temporary fix - override all wxVariant
                    #        typemaps with those found in our own files.
                    ignore_this = False
                    if fn == 'my_typemaps.i':
                        for ig_typemap in settings.ignore_typemaps_from_base:
                            for tn, vn in tm_datatypes:
                                if tn.startswith(ig_typemap):
                                    print('IGNORED %s typemap from %s' % \
                                          (tm_datatypes, fn))
                                    ignore_this = True
                                    break
                            if ignore_this:
                                break

                    # Do not store options, they are usually just
                    # useless precedence=SWIG_TYPECHECK_POINTER for typecheck.
                    if not ignore_this:
                        self.add_typemap(tm_type, tm_datatypes, tm_tempdata,
                                         tm_content)

                    pos = m.end()



def main():
    #fn = sys.argv[1]
    fn = settings.files[0]

    app = creator_app()

    app.process_include_files()

    classes = {}

    temp_cpp_ss = []
    temp_cpp_ss.append('#define SWIG')
    temp_cpp_ss.append('#define CREATE_VCW')

    for s in settings.defines:
        temp_cpp_ss.append('#define %s'%s)

    for s in app.c_define_lines:
        temp_cpp_ss.append(s)

    for fn in settings.files:
        # Create private copies with culled headers
        #tfn = parse_cpp_header.get_temp_filename('h')
        temp_cpp_ss.append(app.load_file(fn))
        #temp_cpp_ss.append('#include <%s>'%fn)

    temp_cpp_ss.append('')

    temp_cpp = '\n'.join(temp_cpp_ss)
    temp_cpp = temp_cpp.replace('#include','//#include')

    classes = cpp_header_parser.parse_header(temp_cpp, settings.include_paths, {})
    if classes is None:
        return

    # Distribute and check class configs
    for cls in classes.itervalues():
        cls_config = settings.class_config.get(cls.name, {})
        cls.cls_config = cls_config

        for k in cls_config.keys():
            if not k in ALLOWED_CLS_CONFIG_KEYS:
                raise AssertionError("'%s' is not supported class_config key"%k)

    # Update derived classes configs
    for cls in classes.itervalues():
        derived_classes = cls.find_derived_classes(classes)
        for ocls in derived_classes:
            if not ocls.cls_config:
                ocls.cls_config = cls.cls_config

    # Add 'pyvirtual' flag to functions for which we should create wrapper
    for cls in classes.itervalues():
        cls_config = cls.cls_config
        for func in cls.func_list:
            if 'virtual' in func.flags:

                excluded = False
                for fn in cls_config.get('excluded_methods', []):
                    if func.name == fn:
                        excluded = True
                        break

                if not excluded:
                    func.flags.add('pyvirtual')

        # Also add new member, - a list of virtual
        # functions that are added (exclusively) in
        # add_wrapper_code-section below
        cls.additional_virtual_functions = []

    #
    # Parse 'add_wrapper_code' section
    add_wrapper_code = getattr(settings, 'add_wrapper_code', None)
    add_wrapper_code_classes = {}
    if add_wrapper_code:
        add_wrapper_code_classes = cpp_header_parser.parse_header(add_wrapper_code, settings.include_paths, {})
        if add_wrapper_code_classes is None:
            return

        #
        # Add function with 'pyvirtual' flag to main classes dictionary straight away
        for class_name, awc_cls in add_wrapper_code_classes.iteritems():
            try:
                cls = classes[class_name]
            except KeyError:
                raise AssertionError("Classes declared in add_wrapper_code "
                                     "section must exist in included header "
                                     "files. Class '%s' did not exist." % \
                                     (class_name))

            derived_classes = cls.find_derived_classes(classes)

            for func in awc_cls.func_list:
                if 'pyvirtual' in func.flags:
                    # Must create wrapper for this
                    cls.additional_virtual_functions.append(func)
                    for dcls in derived_classes:
                        dcls.additional_virtual_functions.append(func)

    v_py_funcnames = set()
    class_codes = []
    funcsigmap = {}

    dbg_func = 'MySWIGOutputDebugString'

    # Add this string to func names when stored into object
    func_appendum = '_t_'
    pfunc_addendum = '_Parent'
    #if settings.logging:
    #else:

    swig_ignores = app.swig_ignores

    for class_name, cls in classes.iteritems():

        cls_config = cls.cls_config
        script_object_member = cls_config.get('script_object_member',
                                              settings.default_script_object_member)

        #print class_name
        #print cls.super_classes

        base_cb_class = None
        fscls = class_name

        while fscls:
            fscls = classes[fscls].find_super_class(classes, settings.classes)
            if fscls:
                base_cb_class = fscls

        if ((not class_name in settings.classes and not base_cb_class) or
             class_name in swig_ignores):
            continue

        class_name_orig = class_name

        py_class_name = None

        for fs,ss in settings.class_renamers:

            re_fs = re.compile(fs)
            if re_fs.match(class_name):
                py_class_name = re_fs.sub(ss, class_name)
                break

        if not py_class_name:
            py_class_name = 'Py%s'%class_name

        print "Creating callback class for '%s' ('%s')"%(class_name,py_class_name)

        hstatics = []
        hdecls = []
        hdecls2 = []
        sstatics = []
        simps = []
        plain_functions = []
        sstaticsets = []
        constructors = []
        cdecls = []
        cimps = []

        #
        # Look for additional functions to implement (from add_wrapper_code-section)
        plain_functions_to_add = []

        for awc_class_name, awc_cls in add_wrapper_code_classes.iteritems():
            if class_name == awc_class_name or cls.find_super_class(classes, [awc_class_name]):
                for func in awc_cls.func_list:
                    if not ('pyvirtual' in func.flags):
                        plain_functions_to_add.append(func)

        for func in plain_functions_to_add:
            fpp = func.decl.find(func.name)-1
            rest_of_decl = func.decl[fpp+1:]

            hdecls.append('    %s'%func.construct_decl())
            plain_functions.append(func)

        plain_function_names = set([func.name for func in plain_functions_to_add])

        # Add functions from parent callback classes
        use_func_list = cls.get_all_functions(classes)
        existing_func_names = set([func.name for func in use_func_list])
        use_func_list.extend(cls.additional_virtual_functions)
        use_func_list.sort(lambda x,y: cmp(x.name, y.name))

        template_vars = {'py_class_name':py_class_name, 'class_name':class_name_orig,
                         'script_object_member':script_object_member}

        for func in use_func_list:

            func_name = func.name

            #if class_name == 'wxPGTextCtrlAndButtonEditor':
            #    print '%s  flags: %s  in_this: %s'%(func_name,repr(func.flags),(func_name in cls.func_names))

            fpp = func.decl.find(func_name)-1
            rest_of_decl = func.decl[fpp+1:]
            ppp = func.decl.find('(')
            args_of_decl = func.decl[ppp:]
            arguments = func.arguments
            args_str_nodefs = ', '.join(['%s %s'%(a[0],a[1]) for a in arguments])
            arg_names_list = ', '.join([a[1] for a in arguments])
            func_post_decl = ''
            if 'const' in func.flags:
                func_post_decl = ' const'

            func_decl_impl = '%s(%s)%s'%(func_name, args_str_nodefs, func_post_decl)

            if arguments:
                args_str_nodefs_cont = ', '+args_str_nodefs
            else:
                args_str_nodefs_cont = ''

            #
            # Constructor
            if func_name == class_name:
                cdecls.append('    %s%s;'%(py_class_name,args_of_decl))
                ss = []
                ss.append('\n%s::%s(%s)'%(py_class_name,py_class_name,args_str_nodefs))
                ss.append('    : %s(%s)'%(class_name,arg_names_list))
                ss.append('{')
                if settings.logging:
                    ss.append('    %s(wxT("%s::%s()"));\n'%(dbg_func,py_class_name,py_class_name))
                ss.append('    Init();')
                ss.append('}')
                cimps.append('\n'.join(ss))
                continue

            # Don't add non-virtual methods or destructors
            if func_name[0] == '~' or not 'pyvirtual' in func.flags:
                continue

            if func_name in plain_function_names:
                raise AssertionError("%s::%s() defined in add_wrapper_code-section conflicted " \
                                     "with virtual function with same name"%(class_name, func_name))

            if not class_name in app.callback_classes:
                app.add_callback_class(class_name, py_class_name, script_object_member)

            # Determine name with which this functions is exposed in Python
            py_func_name = func_name
            if func_name.startswith('Py') and func_name[2].isupper():
                py_func_name = func_name[2:]
            v_py_funcnames.add(py_func_name)

            # Add dummy names instead of empty names, where needed.
            for i in range(0,len(arguments)):
                a = arguments[i]
                if not a[1]:
                    print 'ERROR: You must specify names for all arguments for %s::%s'%(class_name,func.name)
                    #arguments[i] = (a[0],'_arg%i'%i)
                    return

            if func.retval == 'void':
                rets_void = True
                retvals_count = 0
                default_return = 'return;'
                ret_with_func = ''
            else:
                rets_void = False
                retvals_count = 1
                default_return = 'return %s;'%(cpp_header_parser.get_default_value_for_type(func.retval))
                ret_with_func = 'return '

            if not 'pure' in func.flags:
                add_pfunc = True
            else:
                add_pfunc = False

            decl_s = '    virtual %s;'%(func.decl)

            # If it was pure, we need to add it to interface as well, so that SWIG knows the
            # class is, infact non-abstract now.
            if 'pure' in func.flags:
                hdecls2.append(decl_s)

            hdecls.append(decl_s)

            pfimp = ''
            """
            add_pfunc = False
            if add_pfunc:
                s = '    %s %s%s%s;'%(func.retval,func.name,pfunc_addendum,args_of_decl)
                hdecls.append(s)
                hdecls2.append(s)

                ss = []
                ss.append('')
                ss.append('%s %s::%s%s(%s)%s { %s%s::%s(%s); }'%(func.retval,py_class_name,func.name,pfunc_addendum,args_str_nodefs,func_post_decl,ret_with_func,class_name,func.name,arg_names_list))
                pfimp = '\n'.join(ss)
            else:
                pfimp = ''
            """

            #
            # Generate wrapper function implementation code

            #
            # Generate implementation
            #
            si = []
            si.append('')
            si.append('%s %s::%s'%(func.retval,py_class_name,func_decl_impl))
            si.append('{')
            si.append('    wxPyBlock_t blocked = wxPyBeginBlockThreads();')
            if settings.logging:
                si.append('    %s(wxT("%s::%s() entry"));'%(dbg_func,py_class_name,func.name))
            si.append('')

            #
            # Fall-back check
            si.append('    PyObject* cls_ = PyObject_GetAttr((PyObject*)%s, gs___class___Name);'%(script_object_member))

            si.append('    PyObject* funcobj = NULL;')
            si.append('    if ( PyObject_HasAttr(cls_, gs_%s_Name) == 1 ) funcobj = PyObject_GetAttr(cls_, gs_%s_Name);'%(py_func_name, py_func_name))
            si.append('    Py_DECREF(cls_);')
            si.append('    if ( !funcobj || PyObject_HasAttr((PyObject*)%s, gs__super_call_Name) == 1 )'%(script_object_member))
            #si.append('    if ( PyObject_HasAttr(cls_, gs_%s_Name) != 1 || PyObject_HasAttr(m_scriptObject, gs__super_call_Name) == 1 )'%(py_func_name))
            si.append('    {')
            si.append('        wxPyEndBlockThreads(blocked);')

            if not 'pure' in func.flags:
                if settings.logging:
                    si.append('        %s(wxT("%s::%s() exit (fall-back)"));'%(dbg_func,py_class_name,func.name))

                # If class implemented base version, return it
                if func_name in existing_func_names:
                    if not rets_void:
                        si.append('        return %s::%s(%s);'%(class_name,func.name,arg_names_list))
                    else:
                        si.append('        %s::%s(%s);'%(class_name,func.name,arg_names_list))
                        si.append('        return;')
                else:
                    # Apply code from version in add_wrapper_code
                    # (available as func.content)
                    base_func_content = func.content
                    if not base_func_content:
                        raise AssertionError('%s::%s() should have function content'%(class_name, func_name))
                    si.append(cpp_header_parser.indent(base_func_content%template_vars, 8))
            else:
                if settings.logging:
                    si.append('        %s(wxT("%s::%s() exit (not implemented!!!)"));'%(dbg_func,py_class_name,func.name))
                si.append('        PyErr_SetString(PyExc_TypeError,"this method must be implemented");')
                si.append('        %s'%default_return)

            si.append('    }')
            #si.append('    PyObject* funcobj = PyObject_GetAttr(cls_, gs_%s_Name);'%(py_func_name))
            #if settings.logging: si.append('    %s("6");'%dbg_func)

            pre_func = '\n'.join(si)

            si = []

            # Prepare arguments
            prepss, unprepss, retvalprepss, signature = app.get_swig_typeconv_out(arguments, is_callback = True)
            argin_typemaps = len([a_ for a_ in retvalprepss if a_])
            retvals_count += argin_typemaps

            if retvals_count > 1:
                pyrvn = 'tpl'
            else:
                pyrvn = 'res'

            si.append('    PyObject* %s;'%pyrvn)

            # Map new argument list according to callback typemaps used
            used_args = []
            for i in range(0,len(arguments)):
                if prepss[i]:
                    used_args.append(arguments[i])

            # Form typemap-based function signature
            signature = func.retval + signature

            simps.append((signature,pre_func,func,rets_void,pfimp))

            # Don't redo this method, if common parts for function with this signature was already done
            if signature in funcsigmap:
                funcsigmap[signature][3] += 1
                continue

            i = 0
            for arg_type,arg_name in arguments:
                ps = prepss[i]
                if ps:
                    si.append('    PyObject* py_%s;'%arg_name)
                    si.append('%s'%ps)
                i += 1
                #si.append('%s'%app.get_swig_typeconv_out(arg_type, arg_name,'py_%s'%arg_name))

            # Python method call
            #si.append('\n    PyObject* %s = PyObject_CallMethodObjArgs(self, funcname,'%(pyrvn))

            if used_args:
                s_ = ''.join(['py_%s, '%(a[1]) for a in used_args])
            else:
                s_ = ''

            si.append('    %s = PyObject_CallFunctionObjArgs(funcobj, self, %sNULL);'%(pyrvn,s_))

            """
            for arg_type,arg_name in used_args:
                si.append('                                               py_%s,'%arg_name)

            si.append('                                               NULL);\n')
            """
            si.append('    Py_DECREF(funcobj);')

            # Unprepare arguments
            unprepss.reverse()
            lines_added = 0
            for ps in unprepss:
                if ps:
                    si.append('%s'%ps)
                    lines_added += 1

            #if lines_added:
            #    si.append('')

            si.append('    if (PyErr_Occurred()) SWIG_fail;')
            si.append('    {')

            #
            # Return value conversion
            if not rets_void:
                in_prepss, in_unprepss, in_retvalprepss = app.get_swig_typeconv_in([(func.retval,'retval')], is_callback = True)
                si.append('    %s retval;'%func.retval)

            # If argin values were met, we need to prepare to process a sequence
            if retvals_count > 1:
                si.append('    long tpl_count = -1;')
                si.append('    if ( PySequence_Check(tpl) ) {')
                si.append('        PyObject* py_tpl_count = PyInt_FromLong((long)0);')
                si.append('        PySequence_Count(tpl, py_tpl_count);')
                si.append('        tpl_count = PyInt_AsLong(py_tpl_count);')
                si.append('        Py_DECREF(py_tpl_count);')
                si.append('    }')
                si.append('    if ( tpl_count != %i ) {'%retvals_count)
                si.append('        Py_DECREF(tpl);')
                si.append('        PyErr_SetString(PyExc_TypeError, "Expected tuple of %i items as a return value.");'%retvals_count)
                si.append('        SWIG_fail;')
                si.append('    }')
                si.append('')
                si.append('    PyObject* res;')

                ret_handlers = []

                if not rets_void:
                    ret_handlers.append(in_prepss[0])

                ret_handlers.extend(retvalprepss)

                i = 0
                for ps in ret_handlers:
                    if ps:
                        si.append('    res = PySequence_GetItem(%s, %i);'%(pyrvn,i))
                        si.append(ps)
                        si.append('')
                        i += 1

                si.append('    Py_DECREF(tpl);')

            elif retvals_count == 1:
                si.append(in_prepss[0])
            else:
                # DecRef the None-result
                si.append('    Py_DECREF(res);')

            # Check if we need to add fail-label
            need_fail = True
            #need_fail = False
            #for s in si:
            #    if s.find('goto fail') >= 0 or s.find('SWIG_fail') >= 0 or s.find('SWIG_exception_fail') >= 0:
            #        need_fail = True
            #        break

            # Finalize
            if not rets_void:
                si.append('    wxPyEndBlockThreads(blocked);')
                si.append('    return retval;')
                si.append('    }')
                if need_fail:
                    si.append('  fail:')
                    si.append('    if ( PyErr_Occurred() ) PyErr_Print();')
                    si.append('    wxPyEndBlockThreads(blocked);')
                    si.append('    %s'%default_return);
            else:
                si.append('    }')
                if need_fail:
                    si.append('  fail:')
                    si.append('    wxPyEndBlockThreads(blocked);')

            sig_num = len(funcsigmap)
            """rod2 = rest_of_decl[rest_of_decl.find('(')+1:]
            if rod2.find(')') > 0:
                rod2 = (', '+rod2).replace('  ',' ')
            if rod2.endswith('const'):
                rod2 = rod2[:-5]"""
            #common_func_start = '%s _CommonCallback%i(wxPyBlock_t blocked, PyObject* self, PyObject* funcname%s)'%(func.retval,sig_num,args_str_nodefs_cont)
            common_func_start = '%s _CommonCallback%i(wxPyBlock_t blocked, PyObject* self, PyObject* funcobj%s)'%(func.retval,sig_num,args_str_nodefs_cont)

            funcsigmap[signature] = [sig_num,common_func_start,'\n'.join(si),1]

        if not cdecls:
            cdecls.append('    %s();'%py_class_name)
            ss = []
            ss.append('\n%s::%s()'%(py_class_name,py_class_name))
            ss.append('    : %s()'%(class_name))
            ss.append('{')
            if settings.logging:
                ss.append('    %s(wxT("%s::%s()"));'%(dbg_func,py_class_name,py_class_name))
            ss.append('    Init();')
            ss.append('}')
            cimps.append('\n'.join(ss))

        if settings.logging:
            dtor_log = '\n    %s(wxT("%s::~%s()"));'%(dbg_func,py_class_name,py_class_name)
        else:
            dtor_log = ''

        template_vars.update({'func_decls':'\n'.join(hdecls),'ctor_decls':'\n'.join(cdecls),
                         'ctor_imps':'\n'.join(cimps), 'dtor_log':dtor_log})


        # Interface-class code
        scl = app.scl
        scl.append('')
        scl.append('class %s : public %s'%(py_class_name,class_name))
        scl.append('{')
        scl.append('public:')

        if '__init__append' in cls_config:
            init_append_s = '; %s' % cls_config['__init__append']
        else:
            init_append_s = ''

        scl.append('    %%pythonAppend %s "self._SetSelf(self); self._RegisterMethods()%s"' %
                    (py_class_name, init_append_s))
        scl.append(template_vars['ctor_decls'])
        scl.append('%pythoncode {')
        scl.append('    def CallSuperMethod(self, *args, **kwargs):')
        scl.append('        funcname = args[0]')
        scl.append('        args2 = list(args)')
        scl.append('        args2[0] = self')
        scl.append('        self._super_call = True')
        scl.append('        try:')
        scl.append('            res = getattr(%s, funcname)(*args2, **kwargs)'%py_class_name)
        scl.append('        finally:')
        scl.append('            del self._super_call')
        scl.append('        return res')
        scl.append('')
        scl.append('    def _RegisterMethods(self):')
        scl.append('        cls = self.__class__')
        scl.append('        if not hasattr(cls,\'_pyswig_methods_registered\'):')
        scl.append('            cls._pyswig_methods_registered = True')
        scl.append('            ls = [ab for ab in cls.__dict__.iteritems()]')
        scl.append('            for a, b in ls:')
        scl.append('                if not a.startswith(\'_\'):')
        scl.append('                    setattr(cls, \'%%s%s\'%%a, b)'%func_appendum)
        scl.append('}')
        scl.append('    void _SetSelf(PyObject *self);')
        for s_ in hdecls2:
            scl.append(s_)
        # Add code that copies function-objects to self
        scl.append('};')

        class_code = """\

class %(py_class_name)s : public %(class_name)s {
public:
    %(ctor_decls)s
    virtual ~%(py_class_name)s();
    void _SetSelf(PyObject *self);
%(func_decls)s
private:
    void Init() { if ( !gs_funcNamesInitialized ) _InitFuncNames(); }
};

static PyObject* gs_%(py_class_name)s_pyClass = NULL;

%(ctor_imps)s

%(py_class_name)s::~%(py_class_name)s()
{%(dtor_log)s
    if (%(script_object_member)s) { _deleteOwningObject(%(script_object_member)s); %(script_object_member)s = NULL; }
}

void %(py_class_name)s::_SetSelf(PyObject *self)
{
    if ( !gs_%(py_class_name)s_pyClass )
    {
        gs_%(py_class_name)s_pyClass = PyObject_GetAttr(self, gs___class___Name);
        Py_DECREF(gs_%(py_class_name)s_pyClass);
    }
    if ( !%(script_object_member)s ) {
        %(script_object_member)s = self;
        Py_INCREF(self);
    }
}
"""%template_vars

        class_codes.append((class_code, py_class_name, script_object_member, simps, plain_functions, template_vars))


    ss = ['// THIS FILE HAS BEEN AUTO-GENERATED BY %s' % \
          (SCRIPT_NAME.upper())]

    #
    # Add some helper functions
    s = '''\

#ifndef SWIG_IsOK
    #define SWIG_IsOK(r)               (r >= 0)
#endif

#ifndef Py_RETURN_NONE
    #define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
#endif

void _deleteOwningObject(void* obj)
{
    // Crashes sometimes (on app exit, it seems), so we need to disable it
    /*if ( Py_IsInitialized() )
    {
        wxPyBlock_t blocked = wxPyBeginBlockThreads();
        Py_XDECREF((PyObject*)obj);
        wxPyEndBlockThreads(blocked);
    }*/
}
    '''
    ss.append(s)

    #
    # Add code to manage static python string representations of function names
    v_py_funcnames = list(v_py_funcnames)
    v_py_funcnames.sort()
    #ss.append('#include <Streams.h>')
    ss.append('')

    ss.append('static bool gs_funcNamesInitialized = false;')
    ss.append('static PyObject* gs___class___Name = NULL;')
    ss.append('static PyObject* gs___dict___Name = NULL;')
    ss.append('static PyObject* gs__super_call_Name = NULL;')

    # Declarations
    for s in v_py_funcnames:
        ss.append('static PyObject* gs_%s_Name = NULL;'%s)

    ss.append('')
    ss.append('static void _InitFuncNames()')
    ss.append('{')
    ss.append('    gs___dict___Name = PyString_FromString("__dict__");')
    ss.append('    gs___class___Name = PyString_FromString("__class__");')
    ss.append('    gs__super_call_Name = PyString_FromString("_super_call");')

    for s in v_py_funcnames:
        ss.append('    gs_%s_Name = PyString_FromString("%s%s");'%(s,s,func_appendum))
    ss.append('    gs_funcNamesInitialized = true;')
    ss.append('}')
    ss.append('')

    # Generate shared callback function implementations
    for signature,tpl in funcsigmap.iteritems():
        sig_num, common_func_start, func_imp, sig_count = tpl
        if sig_count > 1:
            # Shared implementation
            ss.append('')
            ss.append('%s'%common_func_start)
            ss.append('{')
            ss.append(func_imp)
            ss.append('}')

    # Generate callback class code
    for s, py_class_name, script_object_member, simps, plain_functions, template_vars in class_codes:
        ss.append(s)

        for func in plain_functions:
            ss.append(func.construct_impl_head(class_name=py_class_name))
            ss.append(func.content%template_vars)
            ss.append('')

        for signature, pre_func, func, rets_void, pfimp in simps:
            sig_num, common_func_start, func_imp, sig_count = funcsigmap[signature]

            ss.append(pre_func)

            if sig_count > 1:
                # Shared implementation
                if not rets_void:
                    ret_with_func = 'return '
                else:
                    ret_with_func = ''

                #cc_call_pre = '_CommonCallback%i(blocked, m_scriptObject, gs_%s_Name'%(sig_num,func.name)
                cc_call_pre = '%s_CommonCallback%i(blocked, (PyObject*)%s, funcobj'%(ret_with_func,sig_num, script_object_member)

                if len(func.arguments) > 0:
                    ss.append('    %s, %s);'%(cc_call_pre,', '.join([a[1] for a in func.arguments])))
                else:
                    ss.append('    %s);'%cc_call_pre)
            else:
                # Private implementation
                ss.append(func_imp.replace('self','((PyObject*)%s)'%script_object_member).replace('funcname','gs_%s_Name'%func.name))

            if settings.logging:
                ss.append('    %s(wxT("%s::%s() exit"));'%(dbg_func,py_class_name,func.name))
            ss.append('}')
            ss.append(pfimp)


    # Wrap it inside "SWIG-C-code-indicators", so the code can be included
    # when running SWIG projname.i.
    s = '%%{\n\n%s\n\n%%}\n'%('\n'.join(ss))

    f = file(os.path.join(settings.output_dir,'%s_cbacks.cpp'%settings.projname),'wt')
    f.write(s)
    f.close()

    s = '\n'.join(app.scl)

    f = file(os.path.join(settings.output_dir,'%s_cbacks.i'%settings.projname),'wt')
    f.write(s)
    f.close()

    out_typemaps = app.typemaps['in'].keys()
    out_typemaps.sort()
    #print '\n'.join(out_typemaps)

try:
    main()
finally:
    cpp_header_parser.cleanup()


